/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "attributesviewdelegate.h"
#include "memorynode.h"
#include "attribute.h"
#include "mainwindow.h"
#include "validator.h"
#include "attributesmodel.h"
#include "texteditform.h"
#include <QDebug>
#include <QSpinBox>
#include <QCheckBox>
#include <QMessageBox>
#include <QComboBox>
#include <QLineEdit>
#include <QRegularExpressionValidator>
#include <QLabel>
#include <QPushButton>
#include <QFormLayout>
#include <QFileDialog>
AttributesViewDelegate::AttributesViewDelegate(QObject *parent) :
    QStyledItemDelegate (parent)
{

}

QWidget *AttributesViewDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    const AttributesModel* model = static_cast<const AttributesModel*>(index.model());
    const Attribute* attr = model->getAttribute(index);
    const std::string& attribute_type = attr->getValidator()->getAttributeType();
    QWidget* widget = QStyledItemDelegate::createEditor(parent, option, index);
    if(!index.isValid())
        return widget;

    if(attribute_type == "int")
    {
        QLineEdit * line_edit = qobject_cast<QLineEdit*>(widget);
        // we need to use LineEdit instead of spinbox because it's only for 'int', which can be too small
        // and QIntValidator is too small as well...

        // enum
        const NodeEnumValidator * enum_validator = model->getEnumValidator(index);
        // python enum
        const PyNodeEnumValidator * py_enum_validator = model->getPyEnumValidator(index);
        // custom regex
        const NodeRegexValidator * regex_validator = model->getRegexValidator(index);
        if(enum_validator)
        {
            line_edit->deleteLater();
            QComboBox * combo = new QComboBox(parent);
            for(const std::string& val: enum_validator->getEnums())
            {
                combo->addItem(QString::fromStdString(val));
            }
            widget = combo;
        }
        else if (py_enum_validator)
        {
            line_edit->deleteLater();
            QComboBox * combo = new QComboBox(parent);
            for(const std::string& val: py_enum_validator->getEnums(attr))
            {
                combo->addItem(QString::fromStdString(val));
            }
            widget = combo;
        }
        else if(regex_validator)
        {
            line_edit->setValidator(new QRegularExpressionValidator(QRegularExpression(QString::fromStdString(regex_validator->getRegexStr())), line_edit));
        }
        else
        {
            line_edit->setValidator(new QRegularExpressionValidator(QRegularExpression("(0[xX][0-9a-fA-F]+)|([+-]?[0-9]\\d*|0)"), line_edit));
        }

    }
    else if(attribute_type == "float")
    {
        QDoubleSpinBox * box = qobject_cast<QDoubleSpinBox*>(widget);
        if(!box)
        {
            // this will happen only when value cannot be cast to double or there's no value;
            if(index.data().toString().size() == 0)
            {
                // empty, load QDoubleSpinBox
                widget->deleteLater();
                box = new QDoubleSpinBox(parent);
                widget = box;
            }
            else
            {
                // return original widget (LineEdit?), since probably it won't fit in QDoubleSpinBox (double)
                return widget;
            }
            return widget;
        }

        const NodeRangeValidator* range_validator = model->getRangeValidator(index);
        if(range_validator)
        {
            if(range_validator->hasMax())
                box->setMaximum(range_validator->getMax());
            if(range_validator->hasMin())
                box->setMinimum(range_validator->getMin());
        }
    }
    else if(attribute_type == "str" || attribute_type == "hex")
    {
        QLineEdit * line_edit = qobject_cast<QLineEdit*>(widget);

        // regex
        const NodeRegexValidator * regex_validator = model->getRegexValidator(index);
        if(regex_validator)
        {
            line_edit->setValidator(new QRegularExpressionValidator(QRegularExpression(QString::fromStdString(regex_validator->getRegexStr())), line_edit));
        }
        // enum
        const NodeEnumValidator * enum_validator = model->getEnumValidator(index);
        // python enum
        const PyNodeEnumValidator * py_enum_validator = model->getPyEnumValidator(index);
        if(enum_validator)
        {
            line_edit->deleteLater();
            QComboBox * combo = new QComboBox(parent);
            for(const std::string& val: enum_validator->getEnums())
            {
                combo->addItem(QString::fromStdString(val));
            }
            widget = combo;
        }
        else if (py_enum_validator)
        {
            line_edit->deleteLater();
            QComboBox * combo = new QComboBox(parent);
            for(const std::string& val: py_enum_validator->getEnums(attr))
            {
                combo->addItem(QString::fromStdString(val));
            }
            widget = combo;
        }
    }
    else if (attribute_type == "long_str")
    {
        widget->deleteLater(); // not needed
        TextEditForm* text_editor = new TextEditForm(index.data().toString(), parent);
        widget = text_editor;
    }
    else if(attribute_type == "file")
    {
        widget->deleteLater(); // not needed
        QFileDialog* file_dialog = new QFileDialog(parent);
        QStringList filters;
        filters << tr("Memory map file (*.yaml *.cheby)")
                << tr("Any files (*)");
        file_dialog->setDirectory(MainWindow::getInstance()->getCurrentPath());
        file_dialog->setAcceptMode(QFileDialog::AcceptOpen);
        file_dialog->setFileMode(QFileDialog::ExistingFile);
        file_dialog->setNameFilters(filters);
        file_dialog->setModal(true);
        connect(file_dialog, &QFileDialog::fileSelected, file_dialog, [file_dialog](){file_dialog->setResult(QFileDialog::Accepted);});
        widget = file_dialog;
    }
    else if (attribute_type == "bool")
    {
        QComboBox * combo = qobject_cast<QComboBox*>(widget);
        combo->setItemText(0, "false");
        combo->setItemText(1, "true");
    }
    return widget;
}

void AttributesViewDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    QStyledItemDelegate::setEditorData(editor, index);
}

void AttributesViewDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QFileDialog* file_dialog = qobject_cast<QFileDialog*>(editor);
    TextEditForm* textEditForm = qobject_cast<TextEditForm*>(editor);
    if(file_dialog)
    {
        if(file_dialog->result() == QFileDialog::Accepted)
        {
            QStringList selected_files = file_dialog->selectedFiles();
            const QString& file_path = selected_files[0];
            model->setData(index, file_path);
        }
    }
    else if(textEditForm)
    {
        if(textEditForm->result() == TextEditForm::Accepted && textEditForm->wasChanged())
        {
            model->setData(index, textEditForm->getText());
        }
    }
    else
        QStyledItemDelegate::setModelData(editor, model, index);
}

void AttributesViewDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QFileDialog* file_dialog = qobject_cast<QFileDialog*>(editor);
    TextEditForm* textEditForm = qobject_cast<TextEditForm*>(editor);
    if(!file_dialog && !textEditForm) // set geometry only if it's not a window
    {
        QStyledItemDelegate::updateEditorGeometry(editor, option, index);
        editor->setGeometry(option.rect);
    }
}

void AttributesViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyledItemDelegate::paint(painter, option, index);
}
